#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/log.h"
#include "wx/module.h"
#include "genobots.h"
#include "defines.h"
using namespace std;

// constructor with settings for #rows and #columns
GenoBots::GenoBots(int r, int c) {
  SetRows(r);
  SetColumns(c);
  botCount = 0;
}

// add a bot to the grid
void GenoBots::AddBot(int theRow, int theColumn, wxInt32 stic, superBot *sbot) {
  bots[theRow][theColumn] = new bot(stic, sbot, theSettings->initBotHealth);
  botCount++;
  changed[theRow][theColumn] = true;
}

// fill the changed matrix with 'true'
void GenoBots::ChangedAll() {
  int i, j;
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      changed[i][j] = true;
    }
  }
}

// fill the changed matrix with 'false'
void GenoBots::ChangedNone() {
  int i, j;
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      changed[i][j] = false;
    }
  }
}

// delete bots and food
void GenoBots::Clear() {
  int i, j;
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      delete bots[i][j];
      bots[i][j] = NULL;
      food[i][j] = false;
    }
  }
  botCount = 0;
}

// delete a bot
void GenoBots::DeleteBot(int theRow, int theColumn) {
  delete bots[theRow][theColumn];
  bots[theRow][theColumn] = NULL;
  changed[theRow][theColumn] = true;
  botCount--;
}

bool GenoBots::EmptySpots() {
  int i, j;
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] == NULL) {
        return true;
      }
    }
  }
  return false;
}

// create offspring with evolution
void GenoBots::Evolve(wxInt32 tic) {
  int i, j, k, m, cross, amount;
  vector<bot*> roulette;
  bot *par1, *par2;
  // create proportional roulette wheel for picking mating partners
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] != NULL) {
        // bot exists, save it proportionally
        for(k = 0; k < bots[i][j]->health; k++) {
          roulette.push_back(bots[i][j]);
        }
      }
    }
  }
  // figure out how many children to produce
  amount = (int)((double)botCount / theSettings->evolutionAmount + 0.5);
  if(amount == 0)
    amount = 1;
  for(m = 0; m < amount && EmptySpots(); m++) {
    // select first parent
    par1 = roulette[rand() % roulette.size()];
    // select second parent, same superbot as first
    do {
      par2 = roulette[rand() % roulette.size()];
    } while(par1->mySuperbot != par2->mySuperbot);
    // sort the parents, let the second parent have the largest number of codelines
    if(par2->code.code.size() < par1->code.code.size()) {
      // swap
      bot *tmpBot = par1;
      par1 = par2;
      par2 = tmpBot;
    }
    // create offspring, find empty, random spot
    do {
      // TODO (Ramon#5#): check for full grid
      i = rand() % rows;
      j = rand() % columns;
    } while(bots[i][j] != NULL);
    bots[i][j] = new bot(tic, par1->mySuperbot, theSettings->initBotHealth);
    // create crossover code from parents
    bots[i][j]->code.code.clear();
    cross = rand() % par1->code.code.size();
    for(k = 0; k < cross; k++) {
      bots[i][j]->code.code.push_back(new codeline());
      *bots[i][j]->code.code[bots[i][j]->code.code.size() - 1] = *par1->code.code[k];
    }
    for(k = cross; k < (int)par2->code.code.size(); k++) {
      bots[i][j]->code.code.push_back(new codeline());
      *bots[i][j]->code.code[bots[i][j]->code.code.size() - 1] = *par2->code.code[k];
    }
    // mutate the code
    if(theSettings->mutate) {
      Mutate(tic, i, j);
    }
    theSettings->AddToLog(wxString::Format(_("#%d: Birth (%d,%d)"), tic, i, j));
    changed[i][j] = true;
    botCount++;
  }
}

// get the botcount
int GenoBots::GetBotCount() {
  return botCount;
}

// get number of columns
int GenoBots::GetColumns() {
  return columns;
}

// get number of rows
int GenoBots::GetRows() {
  return rows;
}

// mutate a specific bot's code
void  GenoBots::Mutate(wxInt32 tic, wxInt32 i, wxInt32 j) {
  unsigned int k;
  int randCL, mutation, value;
  bot *tmpBot = bots[i][j];
  for(k = 0; k < tmpBot->code.code.size(); k++) {
    // roll dice to see if this line gets mutated
    if(rand() % theSettings->mutateChance == 0) {
      // pick a line to mutate
      randCL = rand() % 3;
      mutation = ((rand() % 2) * 2) - 1; // -1 or 1
      switch(randCL) {
        case 0:
          value = tmpBot->code.code[k]->intPrm1 + mutation;
          break;
        case 1:
          value = tmpBot->code.code[k]->intPrm2 + mutation;
          break;
        case 2:
          value = tmpBot->code.code[k]->intPrm3 + mutation;
      }
      value = Repair(value, tmpBot->code.code[k]->commandINT, randCL, tmpBot->code.code.size());
      switch(randCL) {
        case 0:
          tmpBot->code.code[k]->intPrm1 = value;
          break;
        case 1:
          tmpBot->code.code[k]->intPrm2 = value;
          break;
        case 2:
          tmpBot->code.code[k]->intPrm3 = value;
      }
      theSettings->AddToLog(wxString::Format(_("#%d: Mutation (%d,%d)"), tic, i, j));
    }
  }
}

// goes to the next instance of the virtual world by evaluating the current status
// and setting up the next instance
bool GenoBots::NextTic(pair<int, int> & selected, wxInt32 tic) {
  int i, j, k, botDir, oldLineNr;
  bool didSomething = false;
  vector< vector<bot* > > tmpGrid;
  // decrease health of the bots
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] != NULL) {
        (bots[i][j]->health)--;
        if(bots[i][j]->health <= 0) {
          // the bot is now dead
          delete bots[i][j];
          bots[i][j] = NULL;
          botCount--;
          changed[i][j] = true;
          if(selected.first == i && selected.second == j) {
            selected.first = -1;
            selected.second = -1;
          }
          theSettings->AddToLog(wxString::Format(_("#%d: Death (%d,%d)"), tic, i, j));
        }
      }
    }
  }
  // set up the temporary matrix
  tmpGrid.resize(rows);
  for(i = 0; i < rows; i++) {
    tmpGrid[i].resize(columns);
  }
  tmpGrid = bots;
  // update every bot
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] != NULL) {
        // execute this bot's code
        bot *tmpBot = bots[i][j];
        oldLineNr = tmpBot->lineNr;
        // turn
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_TURN) {
          switch(tmpBot->code.code[oldLineNr]->intPrm1) {
            case -1:
              // counter-clockwise
              tmpGrid[i][j]->direction = (bots[i][j]->direction + 4 - 1) % 4;
              break;
            case 1:
              // clockwise
              tmpGrid[i][j]->direction = (bots[i][j]->direction + 1) % 4;
              break;
            case 2:
              // 180 degrees
              tmpGrid[i][j]->direction = (bots[i][j]->direction + 2) % 4;
              break;
            default:
              // faulty code! do nothing for now
              break;
          }
          changed[i][j] = true;
          didSomething = true;
          tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
        } else
        // move
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_MOVE) {
          botDir = tmpBot->direction;
          int newI = (i+dir[botDir][0] + rows) % rows;
          int newJ = (j+dir[botDir][1] + columns) % columns;
          if(tmpGrid[newI][newJ] == NULL) {
            tmpGrid[newI][newJ] = bots[i][j];
            tmpGrid[i][j] = NULL;
            changed[newI][newJ] = true;
            changed[i][j] = true;
            didSomething = true;
            // was the bot to be followed?
            if(theSettings->followBot && selected.first == i && selected.second == j) {
              selected.first = newI;
              selected.second = newJ;
            }
            // increase line number
            tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
          }
        } else
        // add
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_ADD) {
          tmpBot->memory[tmpBot->code.code[oldLineNr]->intPrm3 % BOT_NUMMEMCELLS] =
            tmpBot->memory[tmpBot->code.code[oldLineNr]->intPrm1 % BOT_NUMMEMCELLS] + tmpBot->code.code[oldLineNr]->intPrm2;
          // increase line number
          tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
        } else
        // store
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_STORE) {
          tmpBot->memory[tmpBot->code.code[oldLineNr]->intPrm3 % BOT_NUMMEMCELLS] = tmpBot->code.code[oldLineNr]->intPrm2;
          // increase line number
          tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
        } else
        // ifeq
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_IFEQ) {
          if(tmpBot->memory[tmpBot->code.code[oldLineNr]->intPrm1 % BOT_NUMMEMCELLS] == tmpBot->code.code[oldLineNr]->intPrm2) {
            tmpBot->lineNr = tmpBot->code.code[oldLineNr]->intPrm3;
          } else {
            // increase line number
            tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
          }
        } else
        // ifneq
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_IFNEQ) {
          if(tmpBot->memory[tmpBot->code.code[oldLineNr]->intPrm1 % BOT_NUMMEMCELLS] != tmpBot->code.code[oldLineNr]->intPrm2) {
            tmpBot->lineNr = tmpBot->code.code[oldLineNr]->intPrm3;
          } else {
            // increase line number
            tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
          }
        } else
        // attack
        if(tmpBot->code.code[oldLineNr]->commandINT == CMD_ATTACK) {
          botDir = tmpBot->direction;
          int otherI = (i+dir[botDir][0] + rows) % rows;
          int otherJ = (j+dir[botDir][1] + columns) % columns;
          if(bots[otherI][otherJ] != NULL) {
            // there is an opponent
            if(bots[otherI][otherJ]->mySuperbot != bots[i][j]->mySuperbot || tmpBot->code.code[oldLineNr]->intPrm1 == 1) {
              theSettings->AddToLog(wxString::Format(_("#%d: Fight (%d,%d)"), tic, i, j));
              // attack! win chance based on health of both bots
              if(rand() % (bots[i][j]->health + bots[otherI][otherJ]->health) < bots[i][j]->health) {
                // the bot wins the fight
                bots[otherI][otherJ]->health -= theSettings->fightDamage;
                bots[i][j]->health += theSettings->fightDamage;
                if(bots[otherI][otherJ]->health <= 0) {
                  // the other bot is dead
                  theSettings->AddToLog(wxString::Format(_("#%d: Kill (%d,%d)"), tic, otherI, otherJ));
                }
              } else {
                // the other bot wins the fight
                bots[i][j]->health -= theSettings->fightDamage;
                bots[otherI][otherJ]->health += theSettings->fightDamage;
                if(bots[i][j]->health <= 0) {
                  // the bot is dead
                  theSettings->AddToLog(wxString::Format(_("#%d: Killed (%d,%d)"), tic, i, j));
                }
              }
            }
          }
          // increase line number
          tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
        } else {
          // invalid, continue for now
          // increase line number
          tmpBot->lineNr = (oldLineNr + 1) % tmpBot->code.code.size();
        }
      }
    }
  }
  bots = tmpGrid;
  // eat food
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] != NULL && food[i][j]) {
        // eat food
        food[i][j] = false;
        bots[i][j]->health += theSettings->foodHealth;
      }
    }
  }

    // check if any bots died
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] != NULL) {
        if(bots[i][j]->health <= 0) {
          // the bot is now dead
          delete bots[i][j];
          bots[i][j] = NULL;
          botCount--;
          changed[i][j] = true;
          if(selected.first == i && selected.second == j) {
            selected.first = -1;
            selected.second = -1;
          }
          theSettings->AddToLog(wxString::Format(_("#%d: Death (%d,%d)"), tic, i, j));
        }
      }
    }
  }

  // update superbot vectors
  for(k = 0; k < (int)superBots.size(); k++) {
    superBots[k]->numbers.push_back(0);
  }
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(bots[i][j] != NULL) {
        bots[i][j]->mySuperbot->numbers.back()++;
      }
    }
  }

  return didSomething;
}

// places random bots in the world
void GenoBots::Randomize(wxInt32 stic) {
  int i, j, random;
  if(superBots.size()) {
    for(i = 0; i < rows; i++) {
      for(j = 0; j < columns; j++) {
        if(rand() % theSettings->initBotChance == 0) {
          random = (rand() % superBots.size());
          bots[i][j] = new bot(stic, superBots[random], theSettings->initBotHealth);
          botCount++;
        } else {
          bots[i][j] = NULL;
        }
      }
    }
  }
}

// repair a value
int GenoBots::Repair(int value, int commandType, int param, int size) {
  param++;
  // turn
  if(commandType == CMD_TURN) {
    if(param == 1 && (value < -1 || value > 2)) {
      do {
        value += 4;
      } while(value < 0);
      value = value % 4;
    }
  } else
  // store
  if(commandType == CMD_STORE) {
    if(param == 1 && (value < 0 || value > BOT_NUMMEMCELLS - 1)) {
      do {
        value += BOT_NUMMEMCELLS;
      } while(value < 0);
      value = value % BOT_NUMMEMCELLS;
    }
  } else
  // add
  if(commandType == CMD_ADD) {
    if((param == 1 || param == 3) && (value < 0 || value > BOT_NUMMEMCELLS - 1)) {
      do {
        value += BOT_NUMMEMCELLS;
      } while(value < 0);
      value = value % BOT_NUMMEMCELLS;
    }
  } else
  // addm
  if(commandType == CMD_ADDM) {
    if(value < 0 || value > BOT_NUMMEMCELLS - 1) {
      do {
        value += BOT_NUMMEMCELLS;
      } while(value < 0);
      value = value % BOT_NUMMEMCELLS;
    }
  } else
  // ifeq / ifneq
  if(commandType == CMD_IFEQ || commandType == CMD_IFNEQ) {
    if(param == 1 && (value < 0 || value > BOT_NUMMEMCELLS - 1)) {
      do {
        value += BOT_NUMMEMCELLS;
      } while(value < 0);
      value = value % BOT_NUMMEMCELLS;
    }
    if(param == 3 && (value < 0 || value > size - 1)) {
      do {
        value += size;
      } while(value < 0);
      value = value % size;
    }
  } else
  // ifeqm / ifneqm
  if(commandType == CMD_IFEQM || commandType == CMD_IFNEQM) {
    if((param == 1 || param == 2) && (value < 0 || value > BOT_NUMMEMCELLS - 1)) {
      do {
        value += BOT_NUMMEMCELLS;
      } while(value < 0);
      value = value % BOT_NUMMEMCELLS;
    }
    if(param == 3 && (value < 0 || value > size - 1)) {
      do {
        value += size;
      } while(value < 0);
      value = value % size;
    }
  } else
  if(commandType == CMD_JUMPM) {
    if(param == 1 && (value < 0 || value > BOT_NUMMEMCELLS - 1)) {
      do {
        value += BOT_NUMMEMCELLS;
      } while(value < 0);
      value = value % BOT_NUMMEMCELLS;
    }
  }
  else
  if(commandType == CMD_ATTACK) {
    if(param == 1 && (value != 0 && value != 1)) {
      do {
        value += 2;
      } while(value < 0);
      value = value % 2;
    }
  }
  if(value < -1 && param == 0 && commandType == CMD_TURN)
    theSettings->AddToLog(wxString::Format(_("--- Rep1 %d %d %d"), value, commandType, param));
  return value;
}

// randomize bots (different than Randomize, which uses a cleared grid, here bots may already exist)
void GenoBots::SetBotsRandom(wxInt32 stic) {
  int i, j, random;
  if(superBots.size()) {
    for(i = 0; i < rows; i++) {
      for(j = 0; j < columns; j++) {
        if(bots[i][j] == NULL && rand() % theSettings->initBotChance == 0) {
          random = (rand() % superBots.size());
          bots[i][j] = new bot(stic, superBots[random], theSettings->initBotHealth);
          botCount++;
        }
      }
    }
  }
}

// randomize food
void GenoBots::SetFoodRandom() {
  int i, j;
  for(i = 0; i < rows; i++) {
    for(j = 0; j < columns; j++) {
      if(rand() % theSettings->chanceFood == 0)
        food[i][j] = true;
      //else
        //food[i][j] = false;
    }
  }
}

// set #rows and resize the vectors
void GenoBots::SetRows(wxInt32 newRows) {
  rows = newRows;
  bots.resize(rows);
  changed.resize(rows);
  food.resize(rows);
}

// set #columns and resize the vectors
void GenoBots::SetColumns(wxInt32 newColumns) {
  int i;
  columns = newColumns;
  for(i = 0; i < rows; i++) {
    bots[i].resize(columns);
    changed[i].resize(columns);
    food[i].resize(columns);
  }
}
